<?php
/**
 * @abstract :: Current code only deasl with single-hierarchy trees
 * @todo ::Multiple-hierarchy tree
 */
class category_Model_Category extends Zend_Db_Table_Abstract
{
    //only specify the table name, everything else inherits
	protected $_name = "categories";
	protected $_primary = "id";
	protected $_relation_name = "category_relations"; 
	
	
	/**
	 *  
	 * @param categories can be module name "place", "news"... or "general" 
	 */
	public function setTables($categories)
	{
		if (!empty($categories) && $categories != 'general')
		{
			$this->_name = 'categories_' . $categories;
			$this->_relation_name = 'category_relations_' . $categories;
		}
	}
	//TODO : 
	/**
     * @abstract: get all the descedants of an ancestor with id = $rootId
     * @param $rootId: Category Id of the ancestor
     * @param $startDepth: starting level of the descendants
     * @param $endDepth: last level of the descendants
     * @param $sorted: Whether or not we want to sort the returned array by its weight
     *     (weight is determined by the tree_path)
     *     @see tree_path in DB
     * @example getDescendants(0, 1,1, false) returns all the first level categories
     *  
     */
	public function findByPrimary($id)
	{
		if (!$id)
            return null;
        else
		    return $this->fetchRow('id = ' . $this->getAdapter()->quote($id, 'INTEGER'));
	} 
	
	
	public function insertRaw($data, $doValidate = false)
	{
		//TODO: make it atomic & return boolean
		if ($doValidate)
		{
			//TODO: Validate the Data here
		}
	    $c = array();
	    $keys = array ('name', 'description', 'article_id', 'allow_multiselect',
	    				'title', 'weight');
		$c = clu_array_merge($keys, $data);
		try {
			$this->insert($c);
		    $c['id']			= $this->getAdapter()->lastInsertId();
		    $c['pid']			= $data['pid'];
	
		    //insert the parent information
		    $r 			= new category_Model_Relations();
		    $r->setupTableName($this->_relation_name);
	        $x['pid'] 	= $data['pid'];
	        $x['id'] 	= $c['id'];
		    $r->insert($x);
		    
		    $this->updateTreePath($x['id'], $x['pid']);
		    return $c['id'];
		}
		catch (Exception $e)
		{
			return false;
		}
   	}
	
	/**
	 * @param $data::Array Form of the values submited by the form
	 * 		$data['name']
	 * 		$data['description']
	 * 		$data['pid']
	 * 		$data['id']
	 * @param $id: Id of the category
	 */
	public function saveRaw($c, $data, $id, $doValidate)
	{
		//Find the category row
	    if ((!$c) && (!$id))
	    	return false;
	    else if (!$c)
	       	if (!$c = $this->findByPrimary($id))
	       		return false;
	       		
	    if ($doValidate)
	    {
	    	//TODO: Validate Data here
	    }
	       		
		$c->description 		= $data['description'];
		$c->name 				= $data['name'];
	    $c->allow_multiselect 	= $data['allow_multiselect'];
	    $c->title 				= $data['title'];
	    $c->weight				= $data['weight'];
		$c->save(); 	
		return $this->updateTree($id, $data['pid']);    
	}
	
	public function updateTree($id, $newPid)
	{
		//TODO: make it atomic
		//if parent id does not change. No need to update anything else (pid, tree_path)
		$parentId = $this->findParentId($id);
	    if ($parentId == $newPid)
	    	return true;
	    //only update if the user has not *stupidly* made the parent equal itself	    	
		else if($newPid != $id) 
		{	
			$tmp = $this->findByPrimary($newPid);
			$acc = strstr($tmp['tree_path'],"_".$id."_");
    		if (!$acc && $newPid != -1) //move root to another branch
    		{	
    			$descendants = $this->getDescendants($id,1,-1,true);
    			//update root first
            	$this->updateParentandTreePath($id, $newPid);
				//update all the children
    			foreach($descendants as $d)
       				$this->updateParentandTreePath($d['id'], $d['pid']);
    			return true;
    		}
    		else 
    		{	
    			if($descendantsF1 = $this->getDescendants($id,1,1,false))
        			foreach($descendantsF1 as $d)
    					$this->updateParentandTreePath($d['id'],$parentId);
    					
        		if($descendantsF2 = $this->getDescendants($id,2,-1,true))	
            		foreach($descendantsF2 as $d)
    					$this->updateParentandTreePath($d['id'], $d['pid']);
				if($newPid == -1)
            		$this->deleteByPrimary($id);
            	else
                	$this->updateParentandTreePath($id, $newPid);
    			return true;
    		}
		}
		return false;
	}

	public function updateParentandTreePath($id, $pid)
	{
		//TODO: make it atomic
	    $sql = "UPDATE ".$this->_relation_name . " SET pid = " .$this->getAdapter()->quote($pid,'INTEGER').
					" WHERE id = ".$this->getAdapter()->quote($id,'INTEGER');
		$this->getAdapter()->query($sql);
			
	   	$this->updateTreePath($id, $pid);
	}
	
	// return true or false here
	public function updateTreePath($id, $pid)
	{
		if(($pid) == 0)
		{	
			$tree_path = "_0_".$id;
		}
		else
		{	
            $parentCategory = $this->findByPrimary($pid);
            if($pid != $id)
				$tree_path = $parentCategory['tree_path']."_".$id;
			else
				return;
		}
		$sql = "UPDATE ".$this->_name." SET tree_path=" .$this->getAdapter()->quote($tree_path,'STRING').
			" WHERE id = ".$this->getAdapter()->quote($id, 'INTEGER');
		$this->getAdapter()->query($sql);
	}
	
	/**
	 * @param $rootId: Id of the ancestor
	 * @param $startDepth: 0,1,2.. how many levels down from $rootId
	 * @param $endDepth 2,3,4.. how many levels down from $rootId
	 * 			-1 means all the way down the tree
	 * @param $sorted: Whether or not we sort the returned array
	 * @return: array of arrays
	 */
	public function getDescendants($rootId,$startDepth,$endDepth, $sortByWeight)
	{
		$db = $this->getAdapter();
		
		$sql = "SELECT DISTINCT * FROM ".$this->_name;
		$sql = $sql . " WHERE ( tree_path LIKE '%\_" . $rootId . "\_%' OR tree_path LIKE '%\_" . $rootId . "' )";
		if ($sortByWeight)
			$sql = $sql . " ORDER BY weight";
//		die($sql);
		$categories = $db->fetchAll($sql);
		$r = $this->findByPrimary($rootId);
		$ret = array();
		
		foreach($categories as $c)
		{		
			if ($c['id'] != $rootId)
			{
				if ($abc = strstr($c['tree_path'],"_".$rootId."_" ))
				{
					$a = substr_count($abc, '_') - 1;
    				if($startDepth <= $a && ($endDepth >= $a || $endDepth == '-1'))
    				{
    					$c['depth'] = $a;
    			    	$ret[$c['tree_path']] = $c;
    				}
				}			
			}
			else if ($startDepth == 0)
			{
				$c['depth'] = 0;
		    	$ret[$c['tree_path']] = $c;
			}
		}
		/*
		if ($startDepth == 0)
			$ret[$r['tree_path']] = $r;
		*/	
		//get parent id for the category
		foreach ($ret as $k=>$c)
		{
			$sql = "SELECT pid FROM ". $this->_relation_name . " WHERE id=".$this->getAdapter()->quote($c['id'],'INTEGER');
			$pid = $this->getAdapter()->fetchOne($sql);
			$ret[$k]['pid'] = $pid;
		}
		
		if($sortByWeight)
		{
			if ($startDepth == $endDepth)
				return $ret;
			$arr = array();
			if ($endDepth == -1)
			//TODO: just a quick hack
			// find a better way to select from db and get the sorted tree right at once
			// no need to do another step here...
			// my mind is now fucking blocked, i can't think of any better way
			// maybe the db needs to have a separate 
				$this->addItemToSortedTree($arr, $ret, $rootId, $startDepth, $startDepth + 3);
			else if ($startDepth != $endDepth)
				$this->addItemToSortedTree($arr, $ret, $rootId, $startDepth, $endDepth);
			
			return $arr;
		}
		return $ret;
	}
	
	/**
	 * $endDepth: maximum number of levels down the path
	 */ 
	public function addItemToSortedTree(&$arr, $categories, $rootId, $currentDepth, $endDepth)
	{
		if ($currentDepth > $endDepth)
			return;
		else
		{
			foreach ($categories as $path => $cate)
			{
				if ($cate['pid'] == $rootId || ($currentDepth == 0 && $cate['id'] == $rootId))
				{
					$arr[$cate['tree_path']] = $cate;
					$this->addItemToSortedTree($arr, $categories, $cate['id'], $currentDepth + 1, $endDepth);
				}
			}
		}	
		return;
	}
	
	/*
	 * 
	 */
	public function getTree($rootId,$startDepth = 0,$endDepth = -1)
	{
		$descendants = $this->getDescendants($rootId,$startDepth,$endDepth, true);
		//add the display text
		foreach ($descendants as $k=>$d)
		{
			if ($d['id'] != $rootId)
				$d['displayName'] = str_repeat('-- ', $d['depth']) . ' ' . p($d['name']);
			else 
				$d['displayName'] = $d['name'];
			$descendants[$k] = $d;
		}
		return $descendants;
	}
	
	public function findParentId($id)
	{
		$sql = "SELECT pid FROM ".$this->_relation_name." WHERE id = ".$this->getAdapter()->quote($id,'INTEGER') ;
		$find = $this->getAdapter()->fetchRow($sql);
		return $find['pid'];
	}
	
	public function search($data)
	{	
    	return $this->getDescendants($data['pid'],1,-1, false);
	}
	
	public function deleteByPrimary($idArray)
	{
        $db = $this->getAdapter();
        $db->beginTransaction(); 
        try
        {
            $sql1 = "DELETE FROM ".$this->_name." WHERE id IN (". $db->quote($idArray, 'INTEGER') .")" ;
           	$db->query($sql1);
           	$sql2 = "DELETE FROM ".$this->_relation_name." WHERE id IN (".$db->quote($idArray, 'INTEGER') .")" ;
           	$db->query($sql2);
            $db->commit();
            return true;
    	}
        catch (Exception $e)
        {
            $db->rollBack();
            return false;
        }
	}
	
 	/**
 	 * 
 	 * 
 	 * @param: $rootType is rtype (Root Type)
     * getRootType() function: get id of rtype 
     * 
     */
    public function getRootType($rootType)
	{
            $placeRootId = $this->getAdapter()->fetchOne("SELECT id FROM ".$this->_name." WHERE name=".$this->getAdapter()->quote($rootType,'STRING'));
            $placeTypes = $this->getDescendants($placeRootId, 1,1,false);
            return $placeTypes;
    }
    
    public function getCategoryName($cid)
    {
    	$db = $this->getAdapter();
		$sql = "SELECT c.name FROM " . $this->_name ." c WHERE id=".(int)$db->quote($cid,"INT");
		$categories = $db->fetchRow($sql);
		return $categories;        
    }
    
    public function getUserCategory($nodeType)
    {
    	// 0_$userId_$nodeType
    	// TODO: if $admin, $userId might need to be passed in from Controller->Form-> here 
		$sess = new Zend_Session_Namespace(CLU_NS);
		if (!$sess->userId)
			return array('success' => false, 'errorMessage' => __("User not yet logged in"));
		//get category 0_$userId in `categories_user` table
		$ret = $this->getCategoryByNameAndCreateIfNeeded($sess->userId, 0);
    	if (!$ret['success'])
    	{
			return $ret;
    	}
    	return $this->getCategoryByNameAndCreateIfNeeded($nodeType, $ret['id']);
    }
    /*
     * $categoryName: name
     * @parentId: parent id of this category
     * @return: array ("success", "id");
     */
    public function getCategoryByNameAndCreateIfNeeded($categoryName, $parentId)
    {
    	$sql = sprintf("SELECT * FROM `categories_user` AS c, 
    							`category_relations_user` AS cr 
    							WHERE cr.id=c.id
    							AND cr.pid=%d AND c.name='%s'",
    							$parentId, $categoryName);
    	$category = $this->getAdapter()->fetchRow($sql);
    	if (!$category){
    		// create the category here
    		$data = array( 'name' => $categoryName , 
    					   'description' => "",
    					   'allow_multiselect' => false,
    					   'weight' => 10,
    					   'pid' => $parentId
    		); 
    		try
    		{
			    $userCategoryId = $this->insertRaw($data, false);
			    return array ("success" => true, "id" => $userCategoryId, "created-new" => true);
    		}
    		catch (Exception $e)
    		{
    		    return array ("success" => false, "errorMessage" => $e->getMessage());
    		}
    	}
    	else
    		return array("success" => true, "id" => $category['id'], "created-new" => false);
    }
}
?>
